package meshGrammar;

import java.util.ArrayList;

import processing.core.PApplet;
import processing.core.PGraphics;
import simpleGeom.Bounds3D;

public class MeshRelax extends PApplet {
	String path = "/Users/caad/Desktop/";
	  Mesh meshOriginal;
	  String pathMeshOriginal=path+"0914_2_9_15_3_13_58.obj";
	  String pathMeshExcerpt=path+"meshExcertp.obj";
	  String pathMeshResult=path+"meshResult.obj";
	  
	  ArrayList<MeshNode> springNodes;
	  ArrayList<MeshSpring> springs;
	  ArrayList<MeshFace> springFaces;
	  boolean exportFlag;

	  float maxLength = 0.1f;
	  float minLength = 0.001f;
	  
	  
	  boolean firstTime=false;
	  public void setup() {
	    size(1024, 768, P3D);
	    setupDynamics();
	    //testMesh();
	  }
	  public void setupDynamics(){
	    meshOriginal = new Mesh();
	    meshOriginal.loadMesh(pathMeshOriginal);
	    meshOriginal.construct();
	    createSprings();
	  }
	  
	  // only for testing, write the excerptMesh for fast debugging and checking the result
	  public void testMesh(){
	    meshOriginal = new Mesh();
	    meshOriginal.loadMesh(pathMeshOriginal);
	    meshOriginal.construct();
	    int nNodes = meshOriginal.getNNodes();
	    createSprings();
	    if (firstTime){
	      saveObjExcerpt();
	    }
	  }

	// not needed
	  public void fuse(){
	    String pathMeshFinal=path+"meshFinal.obj";
	    Mesh original=new Mesh();
	    original=new Mesh();
	    original.loadMesh(pathMeshOriginal);
	    Mesh result=new Mesh();
	    result.loadMesh(pathMeshResult);
	    for (int i=0;i<result.getNNodes();i++){
	      MeshNode n=result.getNode(i);
	      int index=n.getComment();
	      MeshNode nOriginal=original.getNode(index);
	      nOriginal.setPosition(n.x, n.y, n.z);
	    }
	    original.saveMesh(pathMeshFinal);
	  }
	  
	  public void createSprings() {
	    springs = new ArrayList<MeshSpring>();
	    springNodes = new ArrayList<MeshNode>();
	    springFaces = new ArrayList<MeshFace>();

	    for (int i = 0; i < meshOriginal.getNNodes(); i++) {
	      MeshNode node = meshOriginal.getNode(i);
	      node.setData(null);
	      ArrayList<MeshEdge> edges = node.getEdges();
	      boolean fix = true;
	      for (int j = 0; j < edges.size(); j++) {
	        MeshEdge e = edges.get(j);
	        if (e.getLength() > maxLength) {
	          fix = false;
	          break;
	        }
	      }
	      if (!fix) {
	        Vec3D movement = new Vec3D();
	        node.setData(movement);
	        springNodes.add(node);
	      }
	    }
	    int nNodes=springNodes.size();
	    for (int i = 0; i < nNodes; i++) {
	      MeshNode node = springNodes.get(i);
	      MeshNode[] nbs = node.getNbs();
	      for (int j = 0; j < nbs.length; j++) {
	        MeshNode n=nbs[j];
	        if (n.getData()==null) {
	          springNodes.add(n);
	        }
	      }
	    }
	    for (int i = 0; i < meshOriginal.getNFaces(); i++) {
	      MeshFace face = meshOriginal.getFace(i);
	      for (int j = 0; j < face.getNNodes(); j++) {
	        MeshNode n = face.getNode(j);
	        if (n.data != null) {
	          springFaces.add(face);
	          break;
	        }
	      }
	    }
	    for (int i = 0; i < meshOriginal.getNEdges(); i++) {
	      MeshEdge edge = meshOriginal.getEdge(i);
	      MeshNode n1 = edge.getNode1();
	      MeshNode n2 = edge.getNode2();
	      if (n1.data != null || n2.data != null) {
	        MeshSpring spring = new MeshSpring(n1, n2);
	        spring.desiredDist = minLength;
	        springs.add(spring);
	      }
	    }
	    println("nSprings: "+springs.size());
	    println("nSpringFaces" +springFaces.size());
	  }
	  public void saveObjExcerpt(){
	    for (int i = 0; i < meshOriginal.getNNodes(); i++) {
	      MeshNode node = meshOriginal.getNode(i);
	      node.setComment(i);
	    }
	    Mesh mesh=new Mesh();
	    mesh.nodes=springNodes;
	    mesh.faces=springFaces;
	    mesh.saveMesh(pathMeshExcerpt,true);
	  }
	  public void draw() {
	    background(0,0,255);
	    camera(600,0,0,0,0,0,0,0,1);
	    scale(200);
	    if (exportFlag) {
	      println("start export");
	      meshOriginal.saveMesh(pathMeshResult);
	      exportFlag = false;
	    }
	    dynamics(1);
	    for (int i = 0; i < springFaces.size(); i++) {
	      MeshFace face = springFaces.get(i);
	      face.display3D(g);
	    }
	  }

	  public void keyPressed() {
			if (key == 's') {
				exportFlag = true;
			}
		}

	public void dynamics(int n) {
		// for a better relaxation, forces are calculate first to simulate a simultaneous interaction
		
		// state 1: calculation of the forces
		for (int j=0;j<n;j++){
			for (int i = 0; i < springs.size(); i++) {
				MeshSpring spring = springs.get(i);
				spring.adapt();
			}
		}
		// state 2: movement
		for (int i = 0; i < springNodes.size(); i++) {
			MeshNode node = springNodes.get(i);
			if (node.getData()!=null){
				// node is not fixed, so add the force
				node.add((Vec3D) node.getData());
				// set the force to zero (=system with a high of friction)
				((Vec3D) node.getData()).setPosition(0, 0, 0);
			}
		}
	}

	

	public class MeshSpring {
		MeshNode n1;
		MeshNode n2;
		float desiredDist = 10f;
		float force = 0.01f;// 0.2f

		public MeshSpring(MeshNode n1, MeshNode n2) {
			this.n1 = n1;
			this.n2 = n2;
		}

		public float getStress() {
			return this.getLength() - desiredDist;
		}

		public float getLength() {
			return n1.dist(n2);
		}

		public void display2D(PGraphics g) {
			g.line(n1.x, n1.y, n1.z, n2.x, n2.y, n2.z);
		}

		public void adapt() {
			float dist = getLength();
			Vec3D v = Vec3D.sub(n2, n1);
			float delta = dist - desiredDist;
			if (delta > 0.00f) {
				// calculate the spring-force
				Vec3D f1 = Vec3D.mult(v, (delta / dist) * force);
				Vec3D f2 = Vec3D.mult(f1, -1);
				// add the force to each node
				Vec3D mov1 = (Vec3D) n1.getData();
				if (mov1 != null) {
					mov1.add(f1);
				}
				Vec3D mov2 = (Vec3D) n2.getData();
				if (mov2 != null) {
					mov2.add(f2);
				}
			}
		}
	}
}
